package asgl.files.images {
	import asgl.events.FileEvent;
	
	import flash.display.BitmapData;
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	public class PNGReader extends AbstractImageReader {
		public static const CHUNK_IHDR:uint = 0x49484452;
		public static const CHUNK_PLTE:uint = 0x504C5445;
		public static const CHUNK_TRNS:uint = 0x74524E53;
		public static const CHUNK_IDAT:uint = 0x49444154;
		public static const COLOR_GRAY:int = 0;
		public static const COLOR_RGB:int = 2;
		public static const COLOR_INDEX_RGB:int = 3;
		public static const COLOR_ALPHA_GRAY:int = 4;
		public static const COLOR_ARGB:int = 6;
		public static const FILTER_NONE:int = 0;
		public static const FILTER_SUB:int = 1;
		public static const FILTER_UP:int = 2;
		public static const FILTER_AVERAGE:int = 3;
		public static const FILTER_PAETH:int = 4;
		private var _plteList:Array;
		private var _trnsList:Array;
		private var _bits:int;
		private var _bitsLength:int;
		private var _bitDepth:int;
		private var _colorType:int;
		private var _compressionMethod:int;
		private var _height:int;
		private var _width:int;
		public function PNGReader(bytes:ByteArray=null):void {
			super(bytes);
		}
		public function get bitDepth():int {
			return _bitDepth;
		}
		public function get colorType():int {
			return _colorType;
		}
		public function get compressionMethod():int {
			return _compressionMethod;
		}
		public override function clear():void {
			super.clear();
			_plteList = null;
			_trnsList = null;
			_bitDepth = 0;
			_colorType = 0;
			_compressionMethod = 0;
		}
		public override function read(bytes:ByteArray):void {
			clear();
			try {
				bytes.position = 8;
				bytes.endian = Endian.BIG_ENDIAN;
				var data:ByteArray = new ByteArray();
				data.endian = Endian.BIG_ENDIAN;
				var i:int;
				while (bytes.bytesAvailable>=4) {
					var length:uint = bytes.readUnsignedInt();
					var header:uint = bytes.readUnsignedInt();
					var pos:uint = bytes.position;
					if (header == CHUNK_IHDR) {
						_width = bytes.readUnsignedInt();
						_height = bytes.readUnsignedInt();
						_image = new BitmapData(_width, _height, true, 0x0);
						_bitDepth = bytes.readUnsignedByte();
						_colorType = bytes.readUnsignedByte();
						_compressionMethod = bytes.readUnsignedByte();
						bytes.readUnsignedByte();
						bytes.readUnsignedByte();
					} else if (header == CHUNK_IDAT) {
						data.writeBytes(bytes, pos, length);
					} else if (header == CHUNK_PLTE) {
						_plteList = [];
						for (i = 0; i<length; i+=3) {
							_plteList.push(bytes.readUnsignedByte()<<16|bytes.readUnsignedByte()<<8|bytes.readUnsignedByte());
						}
					} else if (header == CHUNK_TRNS) {
						_trnsList = [];
						for (i = 0; i<length; i++) {
							_trnsList[i] = bytes.readUnsignedByte()<<24;
						}
					}
					bytes.position = pos+length+4;
				}
				data.uncompress();
				if (_colorType == COLOR_RGB) {
					if (_bitDepth == 8) {
						_readRGB8(data);
					}
				} else if (_colorType == COLOR_INDEX_RGB) {
					if (_bitDepth == 1) {
						if (_trnsList == null) {
							_readIndexRGB1(data);
						} else {
							_readIndexARGB1(data);
						}
					} else if (_bitDepth == 2) {
						if (_trnsList == null) {
							_readIndexRGB2(data);
						} else {
							_readIndexARGB2(data);
						}
					} else if (_bitDepth == 4) {
						if (_trnsList == null) {
							_readIndexRGB4(data);
						} else {
							_readIndexARGB4(data);
						}
					} else if (_bitDepth == 8) {
						if (_trnsList == null) {
							_readIndexRGB8(data);
						} else {
							_readIndexARGB8(data);
						}
					}
				} else if (_colorType == COLOR_ARGB) {
					if (_bitDepth == 8) {
						_readARGB8(data);
					}
				}
				_plteList = null;
				_trnsList = null;
				_isCorrectFormat = true;
				this.dispatchEvent(new FileEvent(FileEvent.COMPLETE));
			} catch (e:Error) {
				trace(e.getStackTrace());
				clear();
				this.dispatchEvent(new FileEvent(FileEvent.ERROR, e));
			}
		}
		private function _readARGB8(bytes:ByteArray):void {
			var x:int;
			var color:uint;
			var pa:int;
			var pr:int;
			var pg:int;
			var pb:int;
			var pa1:int;
			var pr1:int;
			var pg1:int;
			var pb1:int;
			var pa2:int;
			var pr2:int;
			var pg2:int;
			var pb2:int;
			var ca:int;
			var cr:int;
			var cg:int;
			var cb:int;
			var alpha:BitmapData = new BitmapData(_image.width, _image.height, false, 0x0);
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						pr = bytes.readUnsignedByte();
						pg = bytes.readUnsignedByte();
						pb = bytes.readUnsignedByte();
						alpha.setPixel(x, y, bytes.readUnsignedByte());
						_image.setPixel32(x, y, 0xFF000000|pr<<16|pg<<8|pb);
					}
				} else if (filter == FILTER_SUB) {
					pr = bytes.readUnsignedByte();
					pg = bytes.readUnsignedByte();
					pb = bytes.readUnsignedByte();
					pa = bytes.readUnsignedByte();
					alpha.setPixel(0, y, pa);
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						cr = pr+bytes.readUnsignedByte()&0xFF;
						cg = pg+bytes.readUnsignedByte()&0xFF;
						cb = pb+bytes.readUnsignedByte()&0xFF;
						ca = pa+bytes.readUnsignedByte()&0xFF;
						alpha.setPixel(x, y, ca);
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pr = cr;
						pg = cg;
						pb = cb;
						pa = ca;
					}
				} else if (filter == FILTER_UP) {
					for (x = 0; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						cr = bytes.readUnsignedByte();
						cg = bytes.readUnsignedByte();
						cb = bytes.readUnsignedByte();
						alpha.setPixel(x, y, alpha.getPixel(x, y-1)+bytes.readUnsignedByte()&0xFF);
						_image.setPixel32(x, y, 0xFF000000|((color>>16&0xFF)+cr&0xFF)<<16|((color>>8&0xFF)+cg&0xFF)<<8|((color&0xFF)+cb&0xFF));
					}
				} else if (filter == FILTER_AVERAGE) {
					color = _image.getPixel32(0, y-1);
					pr = bytes.readUnsignedByte()+((color>>16&0xFF)>>1)&0xFF;
					pg = bytes.readUnsignedByte()+((color>>8&0xFF)>>1)&0xFF;
					pb = bytes.readUnsignedByte()+((color&0xFF)>>1)&0xFF;
					pa = bytes.readUnsignedByte()+(alpha.getPixel(0, y-1)>>1)&0xFF;
					alpha.setPixel(0, y, pa);
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						cr = bytes.readUnsignedByte()+(((color>>16&0xFF)+pr)>>1)&0xFF;
						cg = bytes.readUnsignedByte()+(((color>>8&0xFF)+pg)>>1)&0xFF;
						cb = bytes.readUnsignedByte()+(((color&0xFF)+pb)>>1)&0xFF;
						ca = bytes.readUnsignedByte()+((alpha.getPixel(x, y-1)+pa)>>1)&0xFF;
						alpha.setPixel(x, y, ca);
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pr = cr;
						pg = cg;
						pb = cb;
						pa = ca;
					}
				} else if (filter == FILTER_PAETH) {
					color = _image.getPixel32(0, y-1);
					pa2 = alpha.getPixel(0, y-1);
					pr2 = color>>16&0xFF;
					pg2 = color>>8&0xFF;
					pb2 = color&0xFF;
					pr = bytes.readUnsignedByte()+pr2&0xFF;
					pg = bytes.readUnsignedByte()+pg2&0xFF;
					pb = bytes.readUnsignedByte()+pb2&0xFF;
					pa = bytes.readUnsignedByte()+pa2&0xFF;
					alpha.setPixel(0, y, pa);
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						pa1 = alpha.getPixel(x, y-1);
						pr1 = color>>16&0xFF;
						pg1 = color>>8&0xFF;
						pb1 = color&0xFF;
						cr = bytes.readUnsignedByte()+_paethPredictor(pr, pr1, pr2)&0xFF;
						cg = bytes.readUnsignedByte()+_paethPredictor(pg, pg1, pg2)&0xFF;
						cb = bytes.readUnsignedByte()+_paethPredictor(pb, pb1, pb2)&0xFF;
						ca = bytes.readUnsignedByte()+_paethPredictor(pa, pa1, pa2)&0xFF;
						alpha.setPixel(x, y, ca);
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pa = ca;
						pr = cr;
						pg = cg;
						pb = cb;
						pa2 = pa1;
						pr2 = pr1;
						pg2 = pg1;
						pb2 = pb1;
					}
				} else {
					bytes.position += 4*_width;
				}
			}
			for (x = 0; x<_width; x++){
				for (y = 0; y<_height; y++) {
					_image.setPixel32(x, y, alpha.getPixel(x, y)<<24|_image.getPixel32(x, y)&0xFFFFFF);
				}
			}
			alpha.dispose();
		}
		private function _readIndexARGB1(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 1);
						_image.setPixel32(x, y, _trnsList[index]|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexARGB2(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 2);
						_image.setPixel32(x, y, _trnsList[index]|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexARGB4(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 4);
						_image.setPixel32(x, y, _trnsList[index]|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexARGB8(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = bytes.readUnsignedByte();
						_image.setPixel32(x, y, _trnsList[index]|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexRGB1(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 1);
						_image.setPixel32(x, y, 0xFF000000|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexRGB2(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 2);
						_image.setPixel32(x, y, 0xFF000000|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexRGB4(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				_bitsLength = 0;
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = _readBits(bytes, 4);
						_image.setPixel32(x, y, 0xFF000000|_plteList[index]);
					}
				}
			}
		}
		private function _readIndexRGB8(bytes:ByteArray):void {
			var x:int;
			var index:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						index = bytes.readUnsignedByte();
						_image.setPixel32(x, y, 0xFF000000|_plteList[index]);
					}
				}
			}
		}
		private function _readRGB8(bytes:ByteArray):void {
			var x:int;
			var color:uint;
			var pr:int;
			var pg:int;
			var pb:int;
			var pr1:int;
			var pg1:int;
			var pb1:int;
			var pr2:int;
			var pg2:int;
			var pb2:int;
			var cr:int;
			var cg:int;
			var cb:int;
			for (var y:int = 0; y<_height; y++) {
				var filter:int = bytes.readUnsignedByte();
				if (filter == FILTER_NONE) {
					for (x = 0; x<_width; x++) {
						_image.setPixel32(x, y, 0xFF000000|bytes.readUnsignedByte()<<16|bytes.readUnsignedByte()<<8|bytes.readUnsignedByte());
					}
				} else if (filter == FILTER_SUB) {
					pr = bytes.readUnsignedByte();
					pg = bytes.readUnsignedByte();
					pb = bytes.readUnsignedByte();
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						cr = pr+bytes.readUnsignedByte()&0xFF;
						cg = pg+bytes.readUnsignedByte()&0xFF;
						cb = pb+bytes.readUnsignedByte()&0xFF;
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pr = cr;
						pg = cg;
						pb = cb;
					}
				} else if (filter == FILTER_UP) {
					for (x = 0; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						_image.setPixel32(x, y, 0xFF000000|((color>>16&0xFF)+bytes.readUnsignedByte()&0xFF)<<16|((color>>8&0xFF)+bytes.readUnsignedByte()&0xFF)<<8|((color&0xFF)+bytes.readUnsignedByte()&0xFF));
					}
				} else if (filter == FILTER_AVERAGE) {
					color = _image.getPixel32(0, y-1);
					pr = bytes.readUnsignedByte()+((color>>16&0xFF)>>1)&0xFF;
					pg = bytes.readUnsignedByte()+((color>>8&0xFF)>>1)&0xFF;
					pb = bytes.readUnsignedByte()+((color&0xFF)>>1)&0xFF;
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						cr = bytes.readUnsignedByte()+(((color>>16&0xFF)+pr)>>1)&0xFF;
						cg = bytes.readUnsignedByte()+(((color>>8&0xFF)+pg)>>1)&0xFF;
						cb = bytes.readUnsignedByte()+(((color&0xFF)+pb)>>1)&0xFF;
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pr = cr;
						pg = cg;
						pb = cb;
					}
				} else if (filter == FILTER_PAETH) {
					color = _image.getPixel32(0, y-1);
					pr2 = color>>16&0xFF;
					pg2 = color>>8&0xFF;
					pb2 = color&0xFF;
					pr = bytes.readUnsignedByte()+pr2&0xFF;
					pg = bytes.readUnsignedByte()+pg2&0xFF;
					pb = bytes.readUnsignedByte()+pb2&0xFF;
					_image.setPixel32(0, y, 0xFF000000|pr<<16|pg<<8|pb);
					for (x = 1; x<_width; x++) {
						color = _image.getPixel32(x, y-1);
						pr1 = color>>16&0xFF;
						pg1 = color>>8&0xFF;
						pb1 = color&0xFF;
						cr = bytes.readUnsignedByte()+_paethPredictor(pr, pr1, pr2)&0xFF;
						cg = bytes.readUnsignedByte()+_paethPredictor(pg, pg1, pg2)&0xFF;
						cb = bytes.readUnsignedByte()+_paethPredictor(pb, pb1, pb2)&0xFF;
						_image.setPixel32(x, y, 0xFF000000|cr<<16|cg<<8|cb);
						pr = cr;
						pg = cg;
						pb = cb;
						pr2 = pr1;
						pg2 = pg1;
						pb2 = pb1;
					}
				} else {
					bytes.position += 3*_width;
				}
			}
		}
		//tool function
		/**
		 * c0 = left, c1 = above, c2 = upper left.
		 */
		private function _paethPredictor(c0:int, c1:int, c2:int):int {
			var pa:int = c1-c2;
    		var pb:int = c0-c2;
    		var pc:int = pa+pb;
    		if (pa<0) pa = -pa;
    		if (pb<0) pb = -pb;
    		if (pc<0) pc = -pc;
    		if (pa<=pb && pa<=pc) {
    			return c0;
    		} else if (pb<=pc) {
    			return c1;
    		} else {
    			return c2;
    		}
		}
		private function _readBits(bytes:ByteArray, length:int):int {
			if (_bitsLength == 0) {
				_bits = bytes.readUnsignedByte();
				_bitsLength = 8-length;
				return _bits>>_bitsLength&((2<<(length-1))-1);
			} else if (_bitsLength>=length) {
				_bitsLength -= length;
				return _bits>>_bitsLength&((2<<(length-1))-1);
			} else {
				var temp:int = _bits;
				_bits = bytes.readUnsignedByte();
				var l:int = length-_bitsLength;
				var out:int = temp&((2<<(l-1))-1)<<_bitsLength|_bits>>(8-l)&((2<<(l-1))-1);
				_bitsLength = 8-l;
				return out;
			}
		}
	}
}